package com.tonyodev.fetch2.model;

import com.tonyodev.fetch2.FetchGroup;
import com.tonyodev.fetch2.FetchGroupObserver;
import com.tonyodev.fetch2.Status;
import com.tonyodev.fetch2.database.DownloadInfo;
import com.tonyodev.fetch2.fetch.FetchModulesBuilder;
import com.tonyodev.fetch2core.FetchCoreUtils;
import com.tonyodev.fetch2core.Reason;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
 * description FetchGroupInfo
 *
 * @author liuluming
 * @since 2021-05-25
 */
public class FetchGroupInfo implements FetchGroup {
    private Set<FetchGroupObserver> observerSet;
    @NotNull
    private volatile List<DownloadInfo> downloads;
    @NotNull
    private List<DownloadInfo> queuedDownloads;
    @NotNull
    private List<DownloadInfo> addedDownloads;
    @NotNull
    private List<DownloadInfo> pausedDownloads;
    @NotNull
    private List<DownloadInfo> downloadingDownloads;
    @NotNull
    private List<DownloadInfo> completedDownloads;
    @NotNull
    private List<DownloadInfo> cancelledDownloads;
    @NotNull
    private List<DownloadInfo> failedDownloads;
    @NotNull
    private List<DownloadInfo> deletedDownloads;
    @NotNull
    private List<DownloadInfo> removedDownloads;
    private int id;
    @NotNull
    private String namespace;

    @NotNull
    public List<DownloadInfo> getDownloads() {
        return this.downloads;
    }

    public void setDownloads(@NotNull List<DownloadInfo> value) {
        this.downloads = value;
        queuedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.QUEUED);
        pausedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.PAUSED);
        downloadingDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.DOWNLOADING);
        completedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.COMPLETED);
        cancelledDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.CANCELLED);
        failedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.FAILED);
        deletedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.DELETED);
        removedDownloads = filters(value, downloadInfo -> downloadInfo.getStatus() == Status.REMOVED);

    }

    /**
     * update
     *
     * @param downloads downloads
     * @param triggerDownload triggerDownload
     * @param reason reason
     */
    public void update(@NotNull final List<DownloadInfo> downloads, @Nullable final DownloadInfo triggerDownload, @NotNull final Reason reason) {
        this.setDownloads(downloads);
        if (reason != Reason.DOWNLOAD_BLOCK_UPDATED) {
            FetchModulesBuilder.getInstance().getMainUIHandler().postTask(() -> {
                synchronized (observerSet) {
                    Iterator<FetchGroupObserver> iterator = observerSet.iterator();
                    while (iterator.hasNext()) {
                        FetchGroupObserver next = iterator.next();
                        next.onChanged(downloads, reason);
                        if (triggerDownload != null) {
                            next.onChanged(downloads, triggerDownload, reason);
                        }
                    }
                }
            });
        }

    }

    @NotNull
    public List<DownloadInfo> getQueuedDownloads() {
        return this.queuedDownloads;
    }

    public void setQueuedDownloads(@NotNull List<DownloadInfo> list) {
        this.queuedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getAddedDownloads() {
        return this.addedDownloads;
    }

    public void setAddedDownloads(@NotNull List<DownloadInfo> list) {
        this.addedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getPausedDownloads() {
        return this.pausedDownloads;
    }

    public void setPausedDownloads(@NotNull List<DownloadInfo> list) {
        this.pausedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getDownloadingDownloads() {
        return this.downloadingDownloads;
    }

    public void setDownloadingDownloads(@NotNull List<DownloadInfo> list) {
        this.downloadingDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getCompletedDownloads() {
        return this.completedDownloads;
    }

    public void setCompletedDownloads(@NotNull List<DownloadInfo> list) {
        this.completedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getCancelledDownloads() {
        return this.cancelledDownloads;
    }

    public void setCancelledDownloads(@NotNull List<DownloadInfo> list) {
        this.cancelledDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getFailedDownloads() {
        return this.failedDownloads;
    }

    public void setFailedDownloads(@NotNull List<DownloadInfo> list) {
        this.failedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getDeletedDownloads() {
        return this.deletedDownloads;
    }

    public void setDeletedDownloads(@NotNull List<DownloadInfo> list) {
        this.deletedDownloads = list;
    }

    @NotNull
    public List<DownloadInfo> getRemovedDownloads() {
        return this.removedDownloads;
    }

    public void setRemovedDownloads(@NotNull List<DownloadInfo> list) {
        this.removedDownloads = list;
    }

    /**
     * getGroupDownloadProgress
     *
     * @return true
     */
    public int getGroupDownloadProgress() {
        Iterable<DownloadInfo> iterable = this.getDownloads();
        boolean isTrue;
        DownloadInfo download;
        if (iterable instanceof Collection && ((Collection) iterable).isEmpty()) {
            isTrue = false;
        } else {
            label:
            {
                Iterator it = iterable.iterator();

                while (it.hasNext()) {
                    Object element = it.next();
                    download = (DownloadInfo) element;
                    if (download.getTotal() < 1L) {
                        isTrue = true;
                        break label;
                    }
                }

                isTrue = false;
            }
        }

        if (isTrue) {
            return -1;
        } else {
            long downloaded = 0L;
            long total = 0L;

            for (Iterator iterator = this.getDownloads().iterator(); iterator.hasNext(); total += download.getTotal()) {
                download = (DownloadInfo) iterator.next();
                downloaded += download.getDownloaded();
            }

            return FetchCoreUtils.calculateProgress(downloaded, total);
        }
    }

    @NotNull
    public Set getObservers() {
        return null;
    }

    /**
     * addFetchGroupObservers
     *
     * @param fetchGroupObservers the fetch group observers.
     */
    public void addFetchGroupObservers(@NotNull FetchGroupObserver... fetchGroupObservers) {
        synchronized (observerSet) {
            List<FetchGroupObserver> newFetchGroupObservers = Arrays.asList(fetchGroupObservers);
            List<FetchGroupObserver> addedObservers = new ArrayList<>();
            Iterator<FetchGroupObserver> iterator = newFetchGroupObservers.iterator();
            while (iterator.hasNext()) {
                FetchGroupObserver fetchGroupObserver = iterator.next();
                if (!this.observerSet.contains(iterator.next())) {
                    this.observerSet.add(fetchGroupObserver);
                    addedObservers.add(fetchGroupObserver);
                }
            }

            FetchModulesBuilder.getInstance().getMainUIHandler().postTask(new FetchGroupInfoObservers(addedObservers, this, fetchGroupObservers));
        }
    }

    /**
     * removeFetchGroupObservers
     *
     * @param fetchGroupObservers to be removed.
     */
    public void removeFetchGroupObservers(@NotNull FetchGroupObserver... fetchGroupObservers) {
        synchronized (observerSet) {
            FetchGroupObserver[] mFetchGroupObserver = fetchGroupObservers;
            int length = fetchGroupObservers.length;

            for (int index = 0; index < length; ++index) {
                FetchGroupObserver fetchGroupObserver = mFetchGroupObserver[index];
                this.observerSet.remove(fetchGroupObserver);
            }
        }
    }

    public int getId() {
        return this.id;
    }

    @NotNull
    public String getNamespace() {
        return this.namespace;
    }

    public FetchGroupInfo(int id, @NotNull String namespace) {
        super();
        this.id = id;
        this.namespace = namespace;
        this.observerSet = new LinkedHashSet<>();
        this.downloads = Collections.emptyList();
        this.queuedDownloads = Collections.emptyList();
        this.addedDownloads = Collections.emptyList();
        this.pausedDownloads = Collections.emptyList();
        this.downloadingDownloads = Collections.emptyList();
        this.completedDownloads = Collections.emptyList();
        this.cancelledDownloads = Collections.emptyList();
        this.failedDownloads = Collections.emptyList();
        this.deletedDownloads = Collections.emptyList();
        this.removedDownloads = Collections.emptyList();
    }

    public FetchGroupInfo(Set<FetchGroupObserver> observerSet, @NotNull List<DownloadInfo> downloads, int id, @NotNull String namespace) {
        this.observerSet = observerSet;
        this.downloads = downloads;
        this.id = id;
        this.namespace = namespace;
    }

    public interface Function<T> {
        boolean predicate(T t);
    }

    /**
     * filters
     *
     * @param source source
     * @param function function
     * @param <T> <T>
     * @return true
     */
    private <T> List<T> filters(Iterable<T> source, Function<T> function) {
        List<T> newArrayList = new ArrayList<>();
        for (T t : source) {
            if (function.predicate(t)) {
                newArrayList.add(t);
            }
        }
        return newArrayList;
    }
}


class FetchGroupInfoObservers implements Runnable {
    List<FetchGroupObserver> list;

    FetchGroupInfo mFetchGroupInfo;

    FetchGroupObserver[] FetchGroupObserver;

    FetchGroupInfoObservers(List<FetchGroupObserver> list, FetchGroupInfo mFetchGroupInfo, FetchGroupObserver[] FetchGroupObserver) {
        this.list = list;
        this.mFetchGroupInfo = mFetchGroupInfo;
        this.FetchGroupObserver = FetchGroupObserver;
    }

    public final void run() {
        Iterator<FetchGroupObserver> it = this.list.iterator();

        while (it.hasNext()) {
            FetchGroupObserver addedObserver = it.next();
            addedObserver.onChanged(this.mFetchGroupInfo.getDownloads(), Reason.OBSERVER_ATTACHED);
        }

    }

}
